package tester;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import src.BondsViaUpperNeigbors;
import src.ClosureType;
import src.Context;

public class BondUpperNeighborsTester {
	
	public static void main(String[] args) {
		Double[][] relation1 = {{ 0.0, 1.0, 0.5, 0.5, 0.5 },			
								{ 0.0, 0.5, 0.5, 1.0, 1.0 }};
		Context c1 = new Context(3, relation1);
		
		Double[][] relation2 = {{ 1.0, 1.0, 0.5, 1.0 },			
								{ 1.0, 1.0, 1.0, 0.5 },
								{ 1.0, 0.5, 0.0, 1.0 }};
		Context c2 = new Context(3, relation2);		
		
//		if we want lowerneighbors of extents
		c1.lattice(ClosureType.UP_DOWN);
		
//		if we want lowerneighbors of intents
		c2.lattice(ClosureType.DOWN_UP);
		
		Map<List<Double>, List<List<Double>>> lowerNeighborsC2 = c2.getLowerNeighbors();
		int j = 0;
		System.out.println("C2 - lower neighbors of intents:");
		for (List<Double> intent : c2.getIntents()) {
			System.out.println(++j + ". " + intent);
			List<List<Double>> lowerNeighbors = lowerNeighborsC2.get(intent);
			for (List<Double> lowerNeighbor : lowerNeighbors) {
				System.out.println(lowerNeighbor);
			}
			System.out.println("---------------------------");
		}
		
		List<Double> lowestIntent = new ArrayList<Double>();

		for (List<Double> intent : c2.getIntents()) {
			if (lowerNeighborsC2.get(intent).isEmpty()) {
				lowestIntent = intent;
			}
		}
		
		Map<List<Double>, List<List<Double>>> lowerNeighborsC1 = c1.getLowerNeighbors();
		j = 0;
		System.out.println("C1 - lower neighbors of extents:");
		for (List<Double> extent : c1.getExtents()) {
			System.out.println(++j + ". " + extent);
			List<List<Double>> lowerNeighbors = lowerNeighborsC1.get(extent);
			for (List<Double> lowerNeighbor : lowerNeighbors) {
				System.out.println(lowerNeighbor);
			}
			System.out.println("---------------------------");
		}
		
		List<Double> lowestExtent = new ArrayList<Double>();

		for (List<Double> extent : c1.getExtents()) {
			if (lowerNeighborsC1.get(extent).isEmpty()) {
				lowestExtent = extent;
			}
		}
				
		List<List<Double>> gamma = new ArrayList<List<Double>>();
		for (int i = 0; i < c1.getObjectsCount(); i++) {
			gamma.add(lowestIntent);
		}				
		System.out.println("gamma: " + gamma);
		System.out.println(leftMultiFunctionClosure(gamma, c1, c2));
		
		List<List<Double>> delta = new ArrayList<List<Double>>();
		for (int i = 0; i < c2.getAttributesCount(); i++) {
			delta.add(lowestExtent);
		}				
		System.out.println("delta: " + delta);
		System.out.println(rightMultiFunctionClosure(delta, c1, c2));
		
		System.out.println(isBond(c2, gamma, ClosureType.DOWN_UP));
		System.out.println(isBond(c1, delta, ClosureType.UP_DOWN));
		System.out.println();
		
		for (int i = 0; i < c1.getObjectsCount(); i++) {
			System.out.println("moveB for gamma: " + gamma + " and object index " + i);
			List<List<List<Double>>> setOfGammas = moveB(c1, c2, gamma, i);
			for (List<List<Double>> multiFunction : setOfGammas) {
				System.out.println(multiFunction);
			}	
		}
		
		System.out.println();
		
		for (int i = 0; i < c2.getAttributesCount(); i++) {
			System.out.println("moveA for delta: " + delta + " and attribute index " + i);
			List<List<List<Double>>> setOfDeltas = moveA(c1, c2, delta, i);
			for (List<List<Double>> multiFunction : setOfDeltas) {
				System.out.println(multiFunction);
			}	
		}		
		System.out.println();		
		
		System.out.println("Lattice of bonds:");
		BondsViaUpperNeigbors b = new BondsViaUpperNeigbors(c1, c2);
		b.latticeOfBonds();
		
		Map<List<List<Double>>, Map<Character, Set<List<List<Double>>>>> latticeOfBonds = b.getLatticeOfBonds();
		j = 0;
		for (List<List<Double>> bond : latticeOfBonds.keySet()) {
			System.out.println(++j + ". " + bond);
			
			System.out.println("\tupper neighbors:");
			Set<List<List<Double>>> upperNeigbors = latticeOfBonds.get(bond).get('u');
			if (upperNeigbors != null) {
				for (List<List<Double>> upperNeigbor : upperNeigbors) {
					System.out.println("\t" + upperNeigbor);
				}	
			}
			System.out.println();			
			System.out.println("\tlower neighbors:");
			Set<List<List<Double>>> lowerNeigbors = latticeOfBonds.get(bond).get('l');
			for (List<List<Double>> lowerNeigbor : lowerNeigbors) {
				System.out.println("\t" + lowerNeigbor);	
			}
			System.out.println();
		}
	}
	
	private static void generateFrom(Context c1, Context c2, List<List<Double>> multiFunction, Map<List<List<Double>>, Map<Character, Set<List<List<Double>>>>> lattice) {
		Set<List<List<Double>>> upperNeighbors = upperNeighbors(c1, c2, multiFunction);
		lattice.get(multiFunction).put('u', upperNeighbors);
		
//		find out newly found upper neighbors = get rid of those which have been found previously
		Set<List<List<Double>>> bigN = new HashSet<List<List<Double>>>(); 
		
		for (List<List<Double>> upperNeighbor : lattice.get(multiFunction).get('u')) {
			if (!lattice.keySet().contains(upperNeighbor)) {
				bigN.add(upperNeighbor);
			}
		}
		
//		update lower neigbors info
		for (List<List<Double>> upperNeighbor : lattice.get(multiFunction).get('u')) {
			if (lattice.get(upperNeighbor) == null) {
				lattice.put(upperNeighbor, new HashMap<Character, Set<List<List<Double>>>>());
			}
			
			if (lattice.get(upperNeighbor).get('l') == null) {
				lattice.get(upperNeighbor).put('l', new HashSet<List<List<Double>>>());
			}
			
			lattice.get(upperNeighbor).get('l').add(upperNeighbor);
		}
		
		for (List<List<Double>> newlyFoundNeighbor : bigN) {
			boolean isFullFuzzySet = true;
			
			for (List<Double> fuzzySet : newlyFoundNeighbor) {
				if (notFullFuzzySet(fuzzySet)) {
					isFullFuzzySet = false;
					break;
				}
			}
			
			if (!isFullFuzzySet) {
				generateFrom(c1, c2, newlyFoundNeighbor, lattice);
			}
		}
	}
	
	private static Set<List<List<Double>>> upperNeighbors(Context c1, Context c2, List<List<Double>> multiFunction) {
		Set<List<List<Double>>> upperNeighbors = new HashSet<List<List<Double>>>();
		
		Set<Integer> min1 = new HashSet<Integer>();
		Set<Integer> min2 = new HashSet<Integer>();
		
		for (int b1 = 0; b1 < multiFunction.size(); b1++) {
			if (notFullFuzzySet(multiFunction.get(b1))) {
				min1.add(b1);
			}
		}
		
		List<List<Double>> transposedMultiFunction = transpose(multiFunction);
		for (int a2 = 0; a2 < transposedMultiFunction.size(); a2++) {
			if (notFullFuzzySet(transposedMultiFunction.get(a2))) {
				min2.add(a2);
			}
		}
		
		for (int b1 = 0; b1 < multiFunction.size(); b1++) {
			if (notFullFuzzySet(multiFunction.get(b1))) {
				List<List<List<Double>>> setOfGammas = moveB(c1, c2, multiFunction, b1);				
				
				for (List<List<Double>> gamma : setOfGammas) {
					Set<Integer> increased = new HashSet<Integer>();
					for (int i = 0; i < c1.getObjectsCount(); i++) {
						if (b1 != i && !multiFunction.get(i).equals(gamma.get(i))) {
//							TODO subset
							increased.add(i);
						}
					}
					
					boolean isIntersectionEmpty = true;
					for (int i = 0; i < increased.size(); i++) {
						if (min1.contains(i)) {
							isIntersectionEmpty = false;
							break;
						}
					}
					
					if (isIntersectionEmpty) {
						upperNeighbors.add(gamma);
					} else {
						min1.remove(b1);
					}					
				}				

			}
		}
		
		for (int a2 = 0; a2 < transposedMultiFunction.size(); a2++) {
			if (notFullFuzzySet(transposedMultiFunction.get(a2))) {
				List<List<List<Double>>> setOfDeltas = moveA(c1, c2, transposedMultiFunction, a2);				
				
				for (List<List<Double>> delta : setOfDeltas) {
					Set<Integer> increased = new HashSet<Integer>();
					for (int i = 0; i < c2.getAttributesCount(); i++) {
						if (a2 != i && !transposedMultiFunction.get(i).equals(delta.get(i))) {
//							TODO subset
							increased.add(i);
						}
					}
					
					boolean isIntersectionEmpty = true;
					for (int i = 0; i < increased.size(); i++) {
						if (min2.contains(i)) {
							isIntersectionEmpty = false;
							break;
						}
					}
					
					if (isIntersectionEmpty) {
						upperNeighbors.add(transpose(delta));
					} else {
						min2.remove(a2);
					}					
				}				

			}
		}
		
		return upperNeighbors;
	}
	
	private static boolean notFullFuzzySet(List<Double> fuzzySet) {
		for (int i = 0; i < fuzzySet.size(); i++) {
			if (fuzzySet.get(i) != 1.0) {
				return true;
			}
		}
		return false;
	}
	
	private static List<List<List<Double>>> moveA(Context c1, Context c2, List<List<Double>> delta, int attributeIndex) {	
//		delta - multifunction
		List<List<List<Double>>> setOfDeltas = new ArrayList<List<List<Double>>>();
		
		List<Double> extent = delta.get(attributeIndex);
		
		List<List<Double>> upperNeighbors = c1.getUpperNeighbors().get(extent);

		for (List<Double> upperNeighbor : upperNeighbors) {
			List<List<Double>> newDelta = replaceFuzzySetInMultiFunction(delta, upperNeighbor, attributeIndex);
//			System.out.println(newDelta);
			List<List<Double>> rightClosedNewDelta = rightMultiFunctionClosure(newDelta, c1, c2);
//			System.out.println(rightClosedNewDelta);
			
			boolean isBond = isBond(c2, transpose(rightClosedNewDelta), ClosureType.DOWN_UP);
			if (isBond) {
				setOfDeltas.add(rightClosedNewDelta);
			}
			
//			System.out.println(isBond);
			
//			System.out.println();
		}
	
		return setOfDeltas;
	}
	
	private static List<List<List<Double>>> moveB(Context c1, Context c2, List<List<Double>> gamma, int objectIndex) {	
//		gamma - multifunction
		List<List<List<Double>>> setOfGammas = new ArrayList<List<List<Double>>>();
		
		List<Double> intent = gamma.get(objectIndex);
		
		List<List<Double>> upperNeighbors = c2.getUpperNeighbors().get(intent);

		for (List<Double> upperNeighbor : upperNeighbors) {
			List<List<Double>> newGamma = replaceFuzzySetInMultiFunction(gamma, upperNeighbor, objectIndex);
//			System.out.println(newGamma);
			List<List<Double>> leftClosedNewGamma = leftMultiFunctionClosure(newGamma, c1, c2);
//			System.out.println(leftClosedNewGamma);
			
			boolean isBond = isBond(c1, transpose(leftClosedNewGamma), ClosureType.UP_DOWN);
			if (isBond) {
				setOfGammas.add(leftClosedNewGamma);
			}
			
//			System.out.println(isBond);
			
//			System.out.println();
		}
	
		return setOfGammas;
	}
	
	private static List<List<Double>> replaceFuzzySetInMultiFunction(List<List<Double>> multiFunction, List<Double> fuzzySet, int index) {
		List<List<Double>> newMultiFunction = new ArrayList<List<Double>>(multiFunction);
		newMultiFunction.set(index, fuzzySet);
		return newMultiFunction;		
	}
	
	private static List<List<Double>> rightMultiFunctionClosure(List<List<Double>> multiFunction, Context c1, Context c2) {
		List<List<Double>> rightClosedMultiFunction = new ArrayList<List<Double>>();
		
		rightClosedMultiFunction = transpose(multiFunction);
		
		rightClosedMultiFunction = multiFunctionClosure(rightClosedMultiFunction, c1, c2, ClosureType.DOWN_UP);
		
		rightClosedMultiFunction = transpose(rightClosedMultiFunction);
		
		rightClosedMultiFunction = multiFunctionClosure(rightClosedMultiFunction, c1, c2, ClosureType.UP_DOWN);
		
		return rightClosedMultiFunction;
	}
	
	private static List<List<Double>> leftMultiFunctionClosure(List<List<Double>> multiFunction, Context c1, Context c2) {
		List<List<Double>> leftClosedMultiFunction = new ArrayList<List<Double>>();
		
		leftClosedMultiFunction = transpose(multiFunction);
		
		leftClosedMultiFunction = multiFunctionClosure(leftClosedMultiFunction, c1, c2, ClosureType.UP_DOWN);
		
		leftClosedMultiFunction = transpose(leftClosedMultiFunction);
		
		leftClosedMultiFunction = multiFunctionClosure(leftClosedMultiFunction, c1, c2, ClosureType.DOWN_UP);
		
		return leftClosedMultiFunction;
	}
	
	private static List<List<Double>> multiFunctionClosure(List<List<Double>> multiFunction, Context c1, Context c2, ClosureType closureType) {
		List<List<Double>> closedMultiFunction = new ArrayList<List<Double>>();
		
		if (closureType == ClosureType.DOWN_UP) {
			for (int i = 0; i < c1.getObjectsCount(); i++) {
				List<Double> intent = multiFunction.get(i);				
				intent = c2.closure(intent, closureType);
				closedMultiFunction.add(i, intent);
			}		
		} else if (closureType == ClosureType.UP_DOWN) {
			for (int i = 0; i < c2.getAttributesCount(); i++) {
				List<Double> extent = multiFunction.get(i);
				extent = c1.closure(extent, closureType);
				closedMultiFunction.add(i, extent);
			}
		}		
		return closedMultiFunction;
	}	
	
    private static List<List<Double>> transpose(List<List<Double>> fuzzySets) {
    	List<List<Double>> result = new ArrayList<List<Double>>();
    	
    	for (int i = 0; i < fuzzySets.get(0).size(); i++) {
    		List<Double> temp = new ArrayList<Double>();
    		for (int j = 0; j < fuzzySets.size(); j++) {
    			temp.add(fuzzySets.get(j).get(i));
			}
    		result.add(temp);
		}

    	return result;
    }
    
    private static boolean isBond(Context c, List<List<Double>> multiFunction, ClosureType closureType) {
    	    
		if (multiFunction == null || multiFunction.isEmpty()) {
			throw new RuntimeException("Please provide multifunction which is not empty");
		}
    	
		if (closureType == ClosureType.DOWN_UP) {
			
			if (c.getIntents().isEmpty()) {
				throw new RuntimeException("There are no intents found. Please run method lattice(ClosureType.DOWN_UP) which take care of it!");
			}
			
			if (multiFunction.get(0).size() != c.getIntents().get(0).size()) {
				throw new RuntimeException(String.format("It is impossible to compare fuzzy set from multifunction with size %d and intent with size %d. Try to change closure type!", multiFunction.get(0).size(), c.getIntents().get(0).size()));
			}
			
			for (List<Double> fuzzySet : multiFunction) {
				if (!c.getIntents().contains(fuzzySet)) {
					return false;
				}
			}	
		} else if (closureType == ClosureType.UP_DOWN) {
			
			if (c.getExtents().isEmpty()) {
				throw new RuntimeException("There are no extents found. Please run method lattice(ClosureType.UP_DOWN) which take care of it!");
			}
			
			if (multiFunction.get(0).size() != c.getExtents().get(0).size()) {
				throw new RuntimeException(String.format("It is impossible to compare fuzzy set from multifunction with size %d and extent with size %d. Try to change closure type!", multiFunction.get(0).size(), c.getExtents().get(0).size()));
			}
			
			for (List<Double> fuzzySet : multiFunction) {
				if (!c.getExtents().contains(fuzzySet)) {
					return false;
				}
			}	
		}
		
    	return true;
    }
}
